﻿using NHibernate.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Endos.Wodo.Persistence
{
    internal sealed class QueryableFetch<T> : IFetch<T>
    {
        private readonly IList<IList<Func<IQueryable<T>, IQueryable<T>>>> _listFetch;

        internal QueryableFetch()
        {
            _listFetch = new List<IList<Func<IQueryable<T>, IQueryable<T>>>>();
        }

        public IQueryable<T> Configure(IQueryable<T> queryable)
        {
            return _listFetch.SelectMany(item => item).Aggregate(queryable, (current, func) => func(current));
        }

        public IThenFetch<TProperty> Fetch<TProperty>(Expression<Func<T, TProperty>> expression)
        {
            var listThenFetch = new List<Func<IQueryable<T>, IQueryable<T>>> { q => q.Fetch(expression) };
            _listFetch.Add(listThenFetch);
            return new ThenFetch<TProperty>(listThenFetch);
        }

        public IThenFetch<TProperty> Fetch<TProperty>(Expression<Func<T, IEnumerable<TProperty>>> expression)
        {
            var listThenFetch = new List<Func<IQueryable<T>, IQueryable<T>>> { q => q.FetchMany(expression) };
            _listFetch.Add(listThenFetch);
            return new ThenFetch<TProperty>(listThenFetch);
        }

        private sealed class ThenFetch<TProperty> : IThenFetch<TProperty>
        {
            private readonly List<Func<IQueryable<T>, IQueryable<T>>> _list;

            public ThenFetch(List<Func<IQueryable<T>, IQueryable<T>>> list)
            {
                _list = list;
            }

            public IThenFetch<TPropertyFetch> Fetch<TPropertyFetch>(Expression<Func<TProperty, TPropertyFetch>> expression)
            {
                _list.Add(q =>
                {
                    if (q is INhFetchRequest<T, TProperty>)
                    {
                        return (q as INhFetchRequest<T, TProperty>).ThenFetch(expression);
                    }

                    return q;
                });

                return new ThenFetch<TPropertyFetch>(_list);
            }

            public IThenFetch<TPropertyFetch> Fetch<TPropertyFetch>(Expression<Func<TProperty, IEnumerable<TPropertyFetch>>> expression)
            {
                _list.Add(q =>
                {
                    if (q is INhFetchRequest<T, TProperty>)
                    {
                        return (q as INhFetchRequest<T, TProperty>).ThenFetchMany(expression);
                    }

                    return q;
                });

                return new ThenFetch<TPropertyFetch>(_list);
            }
        }
    }
}
